// Package middleware
/**
* @Project : geektime-basic-go-study
* @File    : login.go
* @IDE     : GoLand
* @Author  : Tvux
* @Date    : 2024/9/10 13:34
**/

package middleware

import (
	ijwt "geektime-basic-go-study/webook/backend/internal/web/jwt"
	"github.com/HJH0924/GenericGo/logger"
	"github.com/HJH0924/GenericGo/set"
	"github.com/gin-gonic/gin"
	"net/http"
)

type LoginJWTMiddlewareBuilder struct {
	// 这里使用自己的另一个项目中所实现的set ^ ^
	ignorePaths *set.HashSet[string]
	ijwt.UserJWTHandler
	logger.Logger
}

// NewLoginJWTMiddlewareBuilder
// builder 模式 不能对用户的调用顺序有任何假设。
func NewLoginJWTMiddlewareBuilder(ujwtHdl ijwt.UserJWTHandler, zl logger.Logger) *LoginJWTMiddlewareBuilder {
	return &LoginJWTMiddlewareBuilder{
		ignorePaths:    set.NewHashSet[string](),
		UserJWTHandler: ujwtHdl,
		Logger:         zl,
	}
}

func (Self *LoginJWTMiddlewareBuilder) IgnorePath(path string) *LoginJWTMiddlewareBuilder {
	Self.ignorePaths.Add(path)
	return Self
}

func (Self *LoginJWTMiddlewareBuilder) Build() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		if Self.ignorePaths.Contains(ctx.Request.URL.Path) {
			// 不需要登录态校验
			return
		}
		accessJWT := Self.ExtractJWTFromHeader(ctx)
		userClaims, err := Self.ParseAccessJWT(accessJWT)
		if err != nil || userClaims.Uid == 0 {
			Self.Debug("LoginJWTMiddlewareBuilder.Build: ParseAccessJWT Failed",
				logger.Field{Key: "error", Val: err},
				logger.Field{Key: "userClaims", Val: userClaims})
			ctx.AbortWithStatus(http.StatusUnauthorized)
			return
		}
		if userClaims.UserAgent != ctx.Request.UserAgent() {
			// 一般来说用户不会出现这个
			// 可能是严重的安全问题，可以考虑记录日志进行监控
			Self.Logger.Warn("LoginJWTMiddlewareBuilder.Build: UserAgent mismatch",
				logger.Field{Key: "expected", Val: userClaims.UserAgent},
				logger.Field{Key: "actual", Val: ctx.Request.UserAgent()})
			ctx.AbortWithStatus(http.StatusUnauthorized)
			return
		}

		exists, err := Self.IsSessionExists(ctx, userClaims.Ssid)
		if err != nil || exists {
			// 要么 redis 有问题，要么已经退出登录
			Self.Logger.Warn("LoginJWTMiddlewareBuilder.Build: IsSessionExists Failed",
				logger.Field{Key: "error", Val: err},
				logger.Field{Key: "exists", Val: exists})
			ctx.AbortWithStatus(http.StatusUnauthorized)
			return
		}

		// 因为这里还顺便对claims里的数据进行了校验，所以可以写到ctx里面，方便其他接口直接获取
		ctx.Set("UserClaims", userClaims)
	}
}

func (Self *LoginJWTMiddlewareBuilder) CheckLogin() gin.HandlerFunc {
	return Self.IgnorePath("/users/signup").
		IgnorePath("/users/login").
		IgnorePath("/users/login_sms/code/send").
		IgnorePath("/users/login_sms").
		IgnorePath("/oauth2/wechat/authurl").
		IgnorePath("oauth2/wechat/callback").
		Build()
}
